import java.rmi.AccessException;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * @author James Marquardt
 * @author Matt Adams
 * @author Justin Wong
 */
public class NodeActual implements Node {   
    
    private static final String NAME_IN_REGISTRY = "endpoint:";
    
    private Registry registry = null;
    
    private Node remoteNode = null;
    
    private HostPort initialNodeReference;
    /**
     * Host and port of this node.
     */
    private HostPort nodeHostPort;
    
    /**
     * The id of this node.
     */
    private int nodeID;
    
    /**
     * The maximum number of nodes in the chord this node belongs to.
     */
    private int maxNodesInChord;
    /**
     * Routing table (including finger table, and successor/predecessor
     * references)
     */
    private NodeReferences references;
    
    //private boolean stabilizing_flag;

    /**
     * Repository for locally stored entries (Like virtual nodes).
     */
    private Entries entries;
    
    public NodeActual(final RemoteNodeRegistry remoteService,
                      final HostPort hostPort) {
        //need to check for null values.
        this.nodeHostPort = hostPort;
        try {
            RegistrationPacket packet = remoteService.registerNode(this.nodeHostPort);
            this.initialNodeReference = packet.initialNodeReference;
            this.maxNodesInChord = packet.m_value;
            this.nodeID = packet.node_id;
            this.references = new NodeReferences(nodeID, hostPort, entries, maxNodesInChord);
            //stabilizing_flag = false;
                 
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }
    
    protected void openConnections() {
        try {
            if (registry == null) {
                registry = LocateRegistry.createRegistry(this.nodeHostPort.port);
            }
            this.remoteNode = (Node) UnicastRemoteObject.exportObject(this);
            this.registry.bind(NAME_IN_REGISTRY + this.nodeHostPort.toString(), this.remoteNode);
            findSuccessor(this.nodeID);
            notifySuccessor();
            
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        } catch (AlreadyBoundException e) {
        	throw new RuntimeException(e);
        }
    }
    
    protected void closeConnections() {
        try {
            UnicastRemoteObject.unexportObject(this, true);
            registry.unbind(NAME_IN_REGISTRY + this.nodeHostPort.toString());
        } catch (AccessException e) {
        } catch (RemoteException e) {
        } catch (NotBoundException e) {
        }
    }

    public void notifySuccessor() {
        if (this.references.getSuccessor() != null) {
            try {
				this.references.getSuccessor().getNodeReferences().setPredecessor(this);
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }
    }

    /**
     * Get this node's host and port information.
     * 
     * @return Returns HostPort object for this node.
     */
    public final HostPort getNodeHostPort() {
        return this.nodeHostPort;
    }
    
    /**
     * Get this node's id.
     * 
     * @return Returns this nodes id.
     */
    public final int getNodeID() {
        return this.nodeID;
    }
    
    /**
     * Returns max number of nodes in chord.
     * 
     * @return Returns max number of nodes in chord.
     */
    public final int getMaxNodesInChord() {
        return this.maxNodesInChord;
    }
    
    /**
     * Sets the finger table.
     */
    public void setFingerTable(){
    	FingerTable table = this.references.getFingerTable();
    	int mValue = this.references.getMValue();
    	int start = 0; 
    	for(int i = 0; i < mValue; i++){
    		start = (int) ((this.nodeID + Math.pow(2, i)) % Math.pow(2,this.maxNodesInChord));
    		Node finger_node = findSuccessor(start);
    		table.setFinger(i, finger_node);
    	}
    }
    
    @Override
    public Node findSuccessor(final int key) {
        if (key < 0 && key < (int) Math.pow(2, this.maxNodesInChord)) {
            throw new IllegalArgumentException("Node ID should be greater than 0 and less than " + 
                                                (int) Math.pow(2, this.maxNodesInChord) + " (2^m). Received ID: " + key);
        }
        // check if the local node is the only node in the network
        Node successor = this.references.getSuccessor();
        Node result = null;
        if (successor == null) {
            result = this;
        } else {
            try {
                // check if the key to look up lies between this node and its successor
                if ((key > this.nodeID && key < successor.getNodeID())
                        || key == successor.getNodeID()) {
                    result = successor;

                } else {  
                    // ask closest preceding node found in local references for closest
                    // preceding node concerning the key to look up
                    Node closestPrecedingNode = this.references.getClosestPrecedingNode(key);
                    try {
                        result = closestPrecedingNode.findSuccessor(key);
                    } catch (RemoteException e) {
                        //Need log for error
                        this.references.removeReference(closestPrecedingNode);
                        result = findSuccessor(key);
                    }
                }
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return result;
    }

    @Override
    public void insertEntry(Entry entry) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void removeEntry(Entry entry) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public boolean containsEntry(Entry entry) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void leaveNetworkNotify(Node predecessor) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void disconnect() {
        // TODO Auto-generated method stub
        
    }
        
    /**
     * Sets this node's host and port data.
     * 
     * @param nodeHostPort The host and port data for this node.
     */
    public final void setNodeHostPort(final HostPort nodeHostPort) {
        this.nodeHostPort = nodeHostPort;
    }
    
    /**
     * Sets this node's id.
     * 
     * @param nodeID The node id.
     */
    public final void setNodeID(final int nodeID) {
        this.nodeID = nodeID;
    }

	@Override
	public NodeReferences getNodeReferences() {
		return this.references;
	}
	
//  try {
//  Node stub = (Node) UnicastRemoteObject.exportObject(this, 0);
//
//  // Bind the remote object's stub in the registry
//  LocateRegistry.createRegistry(nodeHostPort.port);
//  
//  Registry registry = LocateRegistry.getRegistry(nodeHostPort.port);
//  registry.bind("node"+nodeID, stub);
//
//  Log.out("Exported HashTable service as node" + nodeID + " on registry port" +
//        " " + nodeHostPort.port);
//} catch (Exception e) {
//  Log.err("Could not create registry: " + e);
//  System.exit(0);
//}
//updateTables();
//System.out.println("node created: " + nodeID);
//joinNetwork();
//}


//  private void updateTables() {
//  Set<Integer> node_ids = node_mappings.keySet();
//  
//  for(int i : node_ids) {
//      if(i == nodeID) {
//          continue;
//      } else {
//          HostPort the_loc = node_mappings.get(i);
//          String the_host = the_loc.host;
//          int the_port = the_loc.port;
//          Node stub = null;
//          try {
//              Registry registry = LocateRegistry.getRegistry(the_host, the_port);
//              stub = (Node) registry.lookup("node"+i);
//          } catch (Exception e) {
//              e.printStackTrace();
//          }
//          try {
//              stub.stabilize(mapCopy());
//          } catch (RemoteException e) {
//              // TODO Auto-generated catch block
//              e.printStackTrace();
//          }
//      }
//  }
//  
//}
    
}
